module jinji.user.token;

import jinji.common.type,
jinji.db,
jinji.rpc,
jinji.user.model,
jinji.util.crypto,
jinji.util.string;

public import tame.data.base64;

/// 令牌负载
struct Payload {
	/// 用户ID
	long id;
	/// 权限
	uint data;
	/// 过期时间
	ETime expires;
}

static assert(Payload.sizeof == 16);

/// 令牌
struct Token {
	/// 负载
	Payload payload;
	/// 签名
	ubyte[Payload.sizeof] signature;

	alias payload this;
}

/// 令牌base64大小，不包括填充字符
private enum TokenSize = encodedSize(Token.sizeof) - 1;

/++
创建token
Params:
	uid = 用户ID
	perm = 权限
	duration = 有效期（秒）
Returns: 令牌
+/
auto createToken(Id uid, Perm perm, ETime duration) {
	return Payload(uid, perm, nowSec + duration);
}

/++
令牌签名
Params:
	payload = 令牌负载
	key = 密钥
Returns: base64编码的token
+/
char[TokenSize] signToken(in Payload payload, in void[] key) @trusted {
	auto t = Token(payload);
	t.signature = sign!(Payload.sizeof)(toLE(t)[0 .. Token.sizeof], key);
	char[TokenSize + 1] str = void;
	encode(toBytes(t), str);
	return str[0 .. TokenSize];
}

/// 解析token
Payload parseToken(in char[] str) @trusted {
	import jinji.user.service : getUser;

	if (str.length != TokenSize)
		throw new Exception(_("令牌长度错误"));
	char[TokenSize + 1] token = void;
	token[0 .. TokenSize] = str;
	token[TokenSize] = '=';
	Token t = void;
	decode(token, toBytes(t));
	t = fromLE(t);
	if (t.expires <= nowSec)
		throw new Exception(_("令牌已过期"));
	auto s = t.signature;
	t.signature[] = 0;
	const u = getUser!"pwd"(t.id);
	const bytes = sign!(Payload.sizeof)(toLE(t)[0 .. Token.sizeof], u.pwd);
	if (s == bytes &&
		query!(SB.select!"1"
			.from!RevokedToken
			.where("token=$1"))(bytes).empty)
		return t.payload;
	throw new Exception(_("令牌无效"));
}

/// 撤销token
void revokeToken(in char[] token, string desc = "") {
	Payload t = parseToken(token);
	db.insert(RevokedToken(toLE(t), parseId(t.id), cast(Perm)t.data,
			parseDesc(desc), toStdTime(t.expires)));
}
